/*
 * Copyright (C), 2002-2016, roy 学习代码
 * FileName: MinidaoHandler.java
 * Author:   roy
 * Date:     2016年12月1日 下午3:49:29
 * Description: //模块目的、功能描述      
 * History: //修改记录
 * <author>      <time>      <version>    <desc>
 * 修改人姓名             修改时间            版本号                  描述
 */
package com.roy.royminidao.dao.aop;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.ColumnMapRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;

import com.roy.royminidao.dao.annotation.ResultType;
import com.roy.royminidao.dao.annotation.Sql;
import com.roy.royminidao.dao.page.MiniDaoPage;
import com.roy.royminidao.dao.spring.rowMapper.MiniColumnMapRowMapper;
import com.roy.royminidao.dao.spring.rowMapper.MiniColumnOriginalMapRowMapper;
import com.roy.royminidao.dao.util.MiniDaoUtil;
import com.roy.royminidao.dao.util.ParameterNameUtils;

/**
 * 主要实现对注解为sql的注解，获取sql，<br>
 * 〈功能详细描述〉
 *
 * @author roy
 * @see [相关类/方法]（可选）
 * @since [产品/模块版本] （可选）
 */
public class MinidaoHandler implements InvocationHandler {
    private static final Logger logger = LoggerFactory.getLogger(MinidaoHandler.class);
    /**
     * map的关键字类型 三个值
     */
    private String keyType = "origin";
    private boolean formatSql = false;

    private boolean showSql = false;

    private String dbType = "mysql";

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    private NamedParameterJdbcTemplate namedParameterJdbcTemplate;

    private String UPPER_KEY = "upper";

    private String LOWER_KEY = "lower";

    /*
     * (non-Javadoc)
     * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[])
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 返回结果
        Object returnObj = null;
        Map<String, Object> params = new HashMap<String, Object>();
        // 1、获取方法里的参数
        String sql = getParamMap(proxy, method, params, args);
        // 2、获取方法注解上的sql
        // 分页参数
        MiniDaoPage pageSetting = new MiniDaoPage();
        // 3、执行sql，并解析返回结果
        try {
            returnObj = getReturnMinidaoResult(dbType, pageSetting, method, sql, params);
        } catch (Exception e) {
            returnObj = null;
            e.printStackTrace();
        }
        return returnObj;
    }

    private String getParamMap(Object proxy, Method method, Map<String, Object> sqlParamsMap, Object[] args)
            throws Exception {

        // 方法参数是否有参数注解
        // 如果未使用[参数标签]
        if (args != null && args.length >= 1) {
            String[] methodParams = ParameterNameUtils.getMethodParameterNamesByAnnotation(method);
            if (methodParams == null || methodParams.length == 0) {
                throw new Exception("方法参数数目>=2，必须使用：方法标签@Arguments 或  参数标签@param");
            }
            if (methodParams.length != args.length) {
                throw new Exception("方法参数数目>=2，参数必须使用：标签@param");
            }
            int args_num = 0;
            for (String v : methodParams) {
                if (v == null) {
                    throw new Exception("Dao接口定义，所有参数必须使用@param标签~");
                }

                sqlParamsMap.put(v, args[args_num]);
                args_num++;
            }
        } else if (args != null && args.length == 1) {
            // step.2.将args转换成键值对，封装成Map对象
            sqlParamsMap.put("DTO", args[0]);
        }

        String templateSql = null;
        // [2].获取方法的SQL标签
        if (method.isAnnotationPresent(Sql.class)) {
            Sql sql = method.getAnnotation(Sql.class);
            // 如果用户采用自定义标签SQL，则SQL文件无效
            String sqlstr = sql.vaule();
            if (null != sqlstr && !"".equals(sqlstr.trim())) {
                templateSql = sqlstr;
            }
            logger.debug("@Sql------------------------------------------" + sqlstr);
        }
        return templateSql;
    }

    @SuppressWarnings("unchecked")
    private Object getReturnMinidaoResult(String dbType, MiniDaoPage pageSetting, Method method, String executeSql,
            Map<String, Object> paramMap) {
        // step.4.调用SpringJdbc引擎，执行SQL返回值
        // 5.1获取返回值类型[Map/Object/List<Object>/List<Map>/基本类型]
        String methodName = method.getName();
        // update-begin---author:scott----date:20160906------for:增加通过sql判断是否非查询操作--------

        // 如果是查詢操作
        Class<?> returnType = method.getReturnType();
        if (returnType.isPrimitive()) {
            // update-begin---author:scott----date:20160906------for:修复非包装类型，无法传参数问题--------
            Number number = namedParameterJdbcTemplate.queryForObject(executeSql, paramMap, BigDecimal.class);
            // jdbcTemplate.queryForObject(executeSql, BigDecimal.class);
            // update-begin---author:scott----date:20160906------for:修复非包装类型，无法传参数问题--------

            if ("int".equals(returnType.getCanonicalName())) {
                return number.intValue();
            } else if ("long".equals(returnType.getCanonicalName())) {
                return number.longValue();
            } else if ("double".equals(returnType.getCanonicalName())) {
                return number.doubleValue();
            }
        } else if (returnType.isAssignableFrom(List.class) || returnType.isAssignableFrom(MiniDaoPage.class)) {
            int page = pageSetting.getPage();
            int rows = pageSetting.getRows();
            if (page != 0 && rows != 0) {
                if (returnType.isAssignableFrom(MiniDaoPage.class)) {
                    if (paramMap != null) {
                        pageSetting.setTotal(namedParameterJdbcTemplate.queryForObject(getCountSql(executeSql),
                                paramMap, Integer.class));
                    } else {
                        pageSetting.setTotal(jdbcTemplate.queryForObject(getCountSql(executeSql), Integer.class));
                    }
                }
                executeSql = MiniDaoUtil.createPageSql(dbType, executeSql, page, rows);
            }

            RowMapper resultType = getListRealType(method);
            List list;
            if (paramMap != null) {
                list = namedParameterJdbcTemplate.query(executeSql, paramMap, resultType);
            } else {
                list = jdbcTemplate.query(executeSql, resultType);
            }
            if (returnType.isAssignableFrom(MiniDaoPage.class)) {
                pageSetting.setResults(list);
                return pageSetting;
            } else {
                return list;
            }
        } else if (MiniDaoUtil.isWrapClass(returnType)) {
            if (paramMap != null) {
                return namedParameterJdbcTemplate.queryForObject(executeSql, paramMap, returnType);
            } else {
                return jdbcTemplate.queryForObject(executeSql, returnType);
            }
        } else {
            // 对象类型
            RowMapper<?> rm = BeanPropertyRowMapper.newInstance(returnType);
            // RowMapper<?> rm = ParameterizedBeanPropertyRowMapper.newInstance(returnType);
            // ---update-end--author:scott---date:20160909----for:支持spring4---------
            if (paramMap != null) {
                return namedParameterJdbcTemplate.queryForObject(executeSql, paramMap, rm);
            } else {
                return jdbcTemplate.queryForObject(executeSql, rm);
            }
        }
        return null;
    }

    /**
     * 获取总数sql - 如果要支持其他数据库，修改这里就可以
     * 
     * @param sql
     * @return
     */
    private String getCountSql(String sql) {
        return "select count(0) from (" + sql + ") tmp_count";
    }

    /**
     * 根据参数设置map的key大小写
     **/
    private RowMapper<Map<String, Object>> getColumnMapRowMapper() {
        if (getKeyType().equalsIgnoreCase(LOWER_KEY)) {
            return new MiniColumnMapRowMapper();
        } else if (getKeyType().equalsIgnoreCase(UPPER_KEY)) {
            return new ColumnMapRowMapper();
        } else {
            return new MiniColumnOriginalMapRowMapper();
        }
    }

    /**
     * 获取真正的类型
     * 
     * @param genericReturnType
     * @param rowMapper
     * @return
     */
    private RowMapper<?> getListRealType(Method method) {
        ResultType resultType = method.getAnnotation(ResultType.class);
        if (resultType != null) {
            if (resultType.value().equals(Map.class)) {
                return getColumnMapRowMapper();
            }
            // ---update-begin--author:scott---date:20160909----for:支持spring4---------
            return BeanPropertyRowMapper.newInstance(resultType.value());
            // ---update-end--author:scott---date:20160909----for:支持spring4---------
        }
        String genericReturnType = method.getGenericReturnType().toString();
        String realType = genericReturnType.replace("java.util.List", "").replace("<", "").replace(">", "");
        if (realType.contains("java.util.Map")) {
            return this.getColumnMapRowMapper();
        } else if (realType.length() > 0) {
            try {
                // ---update-begin--author:scott---date:20160909----for:支持spring4---------
                return BeanPropertyRowMapper.newInstance(Class.forName(realType));
                // ---update-end--author:scott---date:20160909----for:支持spring4---------
            } catch (ClassNotFoundException e) {
                logger.error(e.getMessage(), e.fillInStackTrace());
                throw new RuntimeException("minidao get class error ,class name is:" + realType);
            }
        }
        return this.getColumnMapRowMapper();
    }

    /**
     * @return the keyType
     */
    public String getKeyType() {
        return keyType;
    }

    /**
     * @param keyType the keyType to set
     */
    public void setKeyType(String keyType) {
        this.keyType = keyType;
    }

    /**
     * @return the formatSql
     */
    public boolean setFormatSql() {
        return formatSql;
    }

    /**
     * @param formatSql the formatSql to set
     */
    public void setFormatSql(boolean formatSql) {
        this.formatSql = formatSql;
    }

    /**
     * @return the showSql
     */
    public boolean getShowSql() {
        return showSql;
    }

    /**
     * @param showSql the showSql to set
     */
    public void setShowSql(boolean showSql) {
        this.showSql = showSql;
    }

    /**
     * @return the dbType
     */
    public String getDbType() {
        return dbType;
    }

    /**
     * @param dbType the dbType to set
     */
    public void setDbType(String dbType) {
        this.dbType = dbType;
    }

}
